import json
import random
import logging
import re
from QQLoginTool.QQtool import OAuthQQ
from django.conf import settings
from django.contrib.auth import login, logout
from django.http import JsonResponse
from rest_framework.generics import CreateAPIView
from rest_framework.views import APIView
from rest_framework_jwt.settings import api_settings
from users_lab.models import User, OAuthQQUser
from users_lab.serializers import UserSerializer, UserAuthSerializer
from users_lab.utils import generate_access_token, check_access_token
logger = logging.getLogger('django')
from django_redis import get_redis_connection
from django.views import View
from celery_tasks.sms.tasks import ccp_send_sms_code


# API: GET /sms_codes/(?P<mobile>1[3-9]\d{9})/
class SMSCodeView(View):
    """发送短信验证码"""

    def get(self, request, mobile):
        """
        避免频繁发送
        生成短信验证码
        保存短信验证码
        发送短信验证码
        响应
        :param request:
        :return:
        """

    # 避免频繁发送
        # 链接redis, 获取链接对象
        redis_conn = get_redis_connection('verify_code')
        # 进入函数后, 先获取存储在 redis 中的数据
        send_flag = redis_conn.get('send_flag_%s' % mobile)
        # 查看数据是否存在, 如果存在, 说明60s没过, 返回
        if send_flag:
            return JsonResponse({'code': 400,
                                      'errmsg': '发送短信过于频繁'})

    # 生成短信验证码
        sms_code = '%06d' % random.randint(0, 999999)
        logger.info(sms_code)
        # 创建管道对象:
        pl = redis_conn.pipeline()

    # 保存短信验证码
        # 利用链接对象, 保存数据到redis, 使用setex函数
        pl.setex('sms_%s' % mobile, 300, sms_code)
        # 往 redis 中写入一个数据, 写入什么不重要, 时间重要
        # 我们给写入的数据设置为60s,如果过期,则会获取不到.
        pl.setex('send_flag_%s' % mobile, 60, 1)
        # 执行管道:
        pl.execute()

    # 发送短信验证码
        ccp_send_sms_code.delay(mobile, sms_code)

    # 响应
        return JsonResponse({"message": "OK"})


# API: GET /usernames/(?P<username>\w{5,20})/count/
class UsernameCountView(View):
    """判断用户名是否存在"""

    def get(self,request, username):
        """
        查询参数
        返回响应
        :param request:
        :return:
        """
    # 查询参数
        try:
            count = User.objects.filter(username=username).count()
        except Exception as e:
            return JsonResponse({'code': 400,
                                 'errmsg': '访问数据库失败'})

    # 返回响应
        return JsonResponse({"username": username,
                             "count": count})


# API: GET /mobiles/(?P<mobile>1[3-9]\d{9})/count/
class MobileCountView(View):
    '''手机号是否存在'''

    def get(self, request, mobile):
        '''
        查询参数
        返回响应
        :param request:
        :param mobile:
        :return:
        '''
    # 查询参数
        try:
            count = User.objects.filter(mobile = mobile).count()
        except Exception as e:
            return JsonResponse({'code': 400,
                                 'errmsg': '访问数据库失败'})

    # 返回响应
        return JsonResponse({"username": mobile,
                             "count": count})


# API: POST /users/
class RegisterView(CreateAPIView):
    '''用户注册'''

    serializer_class = UserSerializer


# API: POST /authorizations/
class UserAuthorizeView(CreateAPIView):
    """用户登录"""

    serializer_class = UserAuthSerializer


# API: GET /logout/
class LogoutView(View):
    """定义退出登录的接口"""

    def delete(self, request):
        """
        清理session
        创建response对象
        调用对象的 delete_cookie 方法, 清除cookie
        返回响应
        """

    # 清理 session
        logout(request)

    # 创建response对象
        response = JsonResponse({'code':0,
                                 'errmsg':'ok'})

    # 调用对象的 delete_cookie 方法, 清除cookie
        response.delete_cookie('username')

    # 返回响应
        return response


# API: GET /oauth/qq/authorization/?next=<登陆后访问页面url>
class QQFirstView(View):
    '''获取QQ登录网址'''

    def get(self, request):
        '''
        设定next
        获取登录页面网址
        返回登录地址
        :param request:
        :return:
        '''

    # 设定next
        # next 表示从哪个页面进入到的登录页面
        # 将来登录成功后，就自动回到那个页面
        next = request.GET.get('next')

    # 获取 QQ 登录页面网址
        # 创建 OAuthQQ 类的对象
        oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID,
                        client_secret=settings.QQ_CLIENT_SECRET,
                        redirect_uri=settings.QQ_REDIRECT_URI,
                        state=next)
        # 调用对象的获取 qq 地址方法
        login_url = oauth.get_qq_url()

    # 返回登录地址
        return JsonResponse({'login_url':login_url})


class QQUserView(APIView):
    """QQ登录接口"""

    # API: GET /oauth/qq/user/?code=xxx
    def get(self, request):
        """获取OpenID并处理"""

    # 获取前端发送过来的 code 参数:
        code = request.GET.get('code')
        if not code:
            # 判断 code 参数是否存在
            return JsonResponse({'code': 400,
                                 'errmsg': '缺少code参数'})


    # 创建工具对象
        # 调用我们安装的 QQLoginTool 工具类
        oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID,
                        client_secret=settings.QQ_CLIENT_SECRET,
                        redirect_uri=settings.QQ_REDIRECT_URI)

        try:
    # 携带 code 向 QQ服务器 请求 access_token
            access_token = oauth.get_access_token(code)


    # 携带 access_token 向 QQ服务器 请求 openid
            openid = oauth.get_open_id(access_token)

        except Exception as e:
            # 如果上面获取 openid 出错, 则验证失败
            logger.error(e)
            # 返回结果
            return JsonResponse({'code': 400,
                                 'errmsg': '获取qq信息失败'})

        try:
            oauth_qq = OAuthQQUser.objects.get(openid=openid)
        except Exception as e:
    # 如果 openid 没绑定美多商城用户
            # 调用我们自定义的方法, 对 openid 进行加密
            # 把 openid 变为 access_token
            access_token = generate_access_token(openid)

            # 把 access_token 返回给前端
            # 注意: 这里一定不能返回 0 的状态码. 否则不能进行绑定页面
            return JsonResponse({'code': 300,
                                 'access_token': access_token})
        else:
    # 如果 openid 已绑定美多商城用户
        # 根据 user 外键, 获取对应的 QQ 用户(user)
            user = oauth_qq.user

    # 实现状态保持
            login(request, user)

            # 组织payload数据的方法
            jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
            # 生成jwt token数据的方法
            jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

            # 组织payload数据
            payload = jwt_payload_handler(user)
    # 生成jwt token
            token = jwt_encode_handler(payload)

            # 创建重定向到主页的对象
            response = JsonResponse({'user_id':user.id,
                                     'username':user.username,
                                     'token':token})

    # 将用户信息写入到 cookie 中，有效期14天
            response.set_cookie('username',
                                user.username,
                                max_age=3600 * 24 * 14)

    # 返回响应
            return response

    def post(self, request):
        """保存绑定QQ登录用户信息"""

        # 接收参数
        dict = json.loads(request.body.decode())
        mobile = dict.get('mobile')
        password = dict.get('password')
        sms_code_client = dict.get('sms_code')
        access_token = dict.get('access_token')

        # 校验参数
        # 判断参数是否齐全
        if not all([mobile, password, sms_code_client]):
            return JsonResponse({'code': 400,
                                 'errmsg': '缺少必传参数'})

        # 判断手机号是否合法
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return JsonResponse({'code': 400,
                                 'errmsg': '请输入正确的手机号码'})

        # 判断密码是否合格
        if not re.match(r'^[0-9A-Za-z]{8,20}$', password):
            return JsonResponse({'code': 400,
                                 'errmsg': '请输入8-20位的密码'})

        # 判断短信验证码是否一致
        # 创建 redis 链接对象:
        redis_conn = get_redis_connection('verify_code')

        # 从 redis 中获取 sms_code 值:
        sms_code_server = redis_conn.get('sms_%s' % mobile)

        # 判断获取出来的有没有:
        if sms_code_server is None:
            # 如果没有, 直接返回:
            return JsonResponse({'code': 400,
                                 'errmsg': '验证码失效'})
        # 如果有, 则进行判断:
        if sms_code_client != sms_code_server.decode():
            # 如果不匹配, 则直接返回:
            return JsonResponse({'code': 400,
                                 'errmsg': '输入的验证码有误'})

        # 调用我们自定义的函数, 检验传入的 access_token 是否正确:
        # 错误提示放在 sms_code_errmsg 位置
        openid = check_access_token(access_token)
        if not openid:
            return JsonResponse({'code': 400,
                                 'errmsg': '缺少openid'})
        # 保存注册数据
        try:
            user = User.objects.get(mobile=mobile)
        except Exception as e:
            # 用户不存在,新建用户
            user = User.objects.create_user(username=mobile,
                                            password=password,
                                            mobile=mobile)
        else:
            # 如果用户存在，检查用户密码
            if not user.check_password(password):
                return JsonResponse({'code': 400,
                                     'errmsg': '输入的密码不正确'})
        # 将用户绑定 openid
        try:
            OAuthQQUser.objects.create(openid=openid,
                                       user=user)
        except Exception as e:
            return JsonResponse({'code': 400,
                                 'errmsg': '往数据库添加数据出错'})
        # 实现状态保持
        login(request, user)

        # 组织payload数据的方法
        jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
        # 生成jwt token数据的方法
        jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

        # 组织payload数据
        payload = jwt_payload_handler(user)
        # 生成jwt token
        token = jwt_encode_handler(payload)

        # 创建重定向到主页的对象
        response = JsonResponse({'user_id': user.id,
                                 'username': user.username,
                                 'token': token})

        # 登录时用户名写入到 cookie，有效期14天
        response.set_cookie('username',
                            user.username,
                            max_age=3600 * 24 * 14)

        # 响应
        return response